package gameManager;

import java.util.Collections;
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.Semaphore;
import javax.swing.SwingWorker;
import common.Move;
import common.Piece;
import common.Side;
import agent.Agent;
import common.DominoState;

public class GameManager implements StateListener {

    public static final int RANDOM_ALGORITHM = 1;
    public static final int ALPHA_BETA = 2;
    private GameState currentState;
    private Agent upperPlayerXPTO;
    private Agent downPlayerXPTO;
    private boolean xptoIsPlaying;
    private boolean restart;
    private Semaphore upperPlayerSemaphore = new Semaphore(1);
    private Semaphore downPlayerSemaphore = new Semaphore(0);
    private boolean gameOngoing = false;
    private GameManagerGUI gui;
    private LinkedList<Piece> pieces;
    private Random randomDealer;
    private LinkedList<Piece> upperPlayerPieces;
    private LinkedList<Piece> downPlayerPieces;

    public GameManager(GameManagerGUI gui) {
        this.gui = gui;
        currentState = new GameState();
        currentState.addGameListener(this);
    }

    public void createUpperPlayerXPTO(int depthLimit, int handsLimit,
            int handsSeed, int algorithm) {
        upperPlayerXPTO = new Agent(handsSeed);
        configureAgent(upperPlayerXPTO, depthLimit, handsLimit, algorithm);
    }

    public void createDownPlayerXPTO(int depthLimit, int handsLimit,
            int handsSeed, int algorithm) {
        downPlayerXPTO = new Agent(handsSeed);
        configureAgent(downPlayerXPTO, depthLimit, handsLimit, algorithm);
    }

    public Piece getUpperPlayerPieceAt(int index) {
        return index < currentState.getUpperPlayerPieces().size() ? currentState
                .getUpperPlayerPieces().get(index) : null;
    }

    public Piece getDownPlayerPieceAt(int index) {
        return index < currentState.getDownPlayerPieces().size() ? currentState
                .getDownPlayerPieces().get(index) : null;
    }

    private void restart(int dealerSeed) {
        upperPlayerSemaphore = new Semaphore(1); // first player to play
        downPlayerSemaphore = new Semaphore(0); // second player to play
        restart = true;
        randomDealer = new Random(dealerSeed);
        pieces = new LinkedList<>();
        upperPlayerPieces = new LinkedList<>();
        downPlayerPieces = new LinkedList<>();
    }

    private void shufflePieces() {
        pieces = DominoState.getAllPiecesList();
        Collections.shuffle(pieces, randomDealer);

        for (int i = 0; i < DominoState.PIECES_PER_PLAYER; i++) {
            Piece piece = pieces.removeFirst();
            upperPlayerPieces.add(piece);
            piece = pieces.removeFirst();
            downPlayerPieces.add(piece);
        }

        if (upperPlayerXPTO != null) {
            upperPlayerXPTO.setInitialPiecesSet(upperPlayerPieces);
        }
        if (downPlayerXPTO != null) {
            downPlayerXPTO.setInitialPiecesSet(downPlayerPieces);
        }
    }

    private void playerWithHigherDoublePieceStarts() {
        byte startPiece = DominoState.DOMINO_ORDER;
        while (true) {
            Piece doublePiece = new Piece(startPiece, startPiece);
            if (upperPlayerPieces.contains(doublePiece)) {
                try {
                    upperPlayerSemaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                downPlayerSemaphore.release();
                Move move = new Move(Side.LEFT, doublePiece);
                if (currentState.placePiece(Side.LEFT, doublePiece)) {
                    if (upperPlayerXPTO != null) {
                        upperPlayerXPTO.notifyAgentMove(move);
                    }
                    if (downPlayerXPTO != null) {
                        downPlayerXPTO.notifyOpponentMove(move);
                    }
                }

                break;
            }
            if (downPlayerPieces.contains(new Piece(startPiece, startPiece))) {
                Move move = new Move(Side.LEFT, doublePiece);
                if (currentState.placePiece(Side.LEFT, doublePiece)) {
                    if (downPlayerXPTO != null) {
                        downPlayerXPTO.notifyAgentMove(move);
                    }
                    if (upperPlayerXPTO != null) {
                        upperPlayerXPTO.notifyOpponentMove(move);
                    }
                }
                break;
            }
            startPiece--;
        }
    }

    public void startGame(int dealerSeed) {
        restart(dealerSeed);

        shufflePieces();

        while (xptoIsPlaying)
			; // Waits for the end of a move.
        currentState.restart(upperPlayerPieces, downPlayerPieces);

        restart = false;

        playerWithHigherDoublePieceStarts();

        if (upperPlayerXPTO != null) {
            upperPlayerMoves();
        }

        if (downPlayerXPTO != null) {
            downPlayerMoves();
        }

        gameOngoing = true;
    }

    private void upperPlayerMoves() {
        SwingWorker worker = new SwingWorker<Void, Void>() {
            @Override
            public Void doInBackground() {
                try {
                    while (!currentState.isEndOfGameState() && !restart) {
                        upperPlayerSemaphore.acquire();
                        if (currentState.isEndOfGameState() || restart) {
                            return null;
                        }
                        xptoIsPlaying = true;

                        if (upperPlayerXPTO != null
                                && currentState.upperPlayerCanPlay()) {
                            Move move = upperPlayerXPTO.play();
                            if (currentState.placePiece(move.getSide(),
                                    move.getPiece())) {
                                // We must do the following test because the
                                // game may end when we place a piece.
                                // When the game ends, xptos are nulled (see
                                // stateChanged).
                                if (upperPlayerXPTO != null) {
                                    upperPlayerXPTO.notifyAgentMove(move);
                                }
                                if (downPlayerXPTO != null) {
                                    downPlayerXPTO.notifyOpponentMove(move);
                                }
                            }
                        }
                        xptoIsPlaying = false;
                        if (currentState.downPlayerCanPlay() || currentState.isEndOfGameState()) {
                            downPlayerSemaphore.release();
                        } else {
                            upperPlayerSemaphore.release();
                        }

                    }
                    gameOngoing = false;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        };

        worker.execute();
    }

    private void downPlayerMoves() {
        SwingWorker worker = new SwingWorker<Void, Void>() {
            @Override
            public Void doInBackground() {
                try {
                    while (!currentState.isEndOfGameState() && !restart) {
                        downPlayerSemaphore.acquire();
                        if (currentState.isEndOfGameState() || restart) {
                            return null;
                        }
                        xptoIsPlaying = true;

                        if (downPlayerXPTO != null
                                && currentState.downPlayerCanPlay()) {
                            Move move = downPlayerXPTO.play();
                            if (currentState.
                                    placePiece(move.
                                            getSide(),
                                            move.
                                            getPiece())) {
                                // We must do the following test because the
                                // game may end when we place a piece.
                                // When the game ends, xptos are nulled (see
                                // stateChanged).
                                if (downPlayerXPTO != null) {
                                    downPlayerXPTO.notifyAgentMove(move);
                                }
                                if (upperPlayerXPTO != null) {
                                    upperPlayerXPTO.notifyOpponentMove(move);
                                }
                            }
                        }
                        xptoIsPlaying = false;
                        if (currentState.upperPlayerCanPlay() || currentState.isEndOfGameState()) {
                            upperPlayerSemaphore.release();
                        } else {
                            downPlayerSemaphore.release();
                        }

                    }
                    gameOngoing = false;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        };

        worker.execute();
    }

    public void humanMove(Piece piece, Side side) {
        if (!xptoIsPlaying && !currentState.isEndOfGameState()
                && currentState.isValidMove(side, piece)) {
            currentState.placePiece(side, piece);
            if (upperPlayerXPTO != null) {
                upperPlayerXPTO.notifyOpponentMove(new Move(side, piece));
                upperPlayerSemaphore.release();

            } else if (downPlayerXPTO != null) {
                downPlayerXPTO.notifyOpponentMove(new Move(side, piece));
                downPlayerSemaphore.release();
            }
        }
    }

    @Override
    public void stateChanged(StateEvent pe) {
        if (currentState.isEndOfGameState()) {
            gui.showWinner(currentState.getWinner());
            upperPlayerXPTO = downPlayerXPTO = null;
            gameOngoing = false;
        }
    }

    public GameState getCurrentState() {
        return currentState;
    }

    public boolean isGameOngoing() {
        return gameOngoing;
    }

    private void configureAgent(Agent agent, int depthLimit, int handsLimit,
            int algorithm) {
        if (algorithm == RANDOM_ALGORITHM) {
            agent.useRandomAlgorithm();
        } else {
            agent.useAlfabeta();
            agent.setHandsLimit(handsLimit);
            agent.setSearchDepth(depthLimit);
        }
    }
}
